雞隻健康追蹤資訊系統的第一道關卡就是讓使用者登入,由於系統是建立在ASP.NET MVC 5之上,因此對於使用者的登入及權限控管的機制都將採用ASP.NET Identity。如果透過VS2013新建立一個MVC專案,可以發現裡頭已經包含了一個基本的登入驗證機制,但是我們都知道,現實世界的需求不會如同範例這麼地單純,資料庫不一定會用MS SQL Server,會員資料也會需要記錄許多額外重要資訊。因此接下來將介紹該如何手工打造出利用ASP.NET Identity的登入及驗證機制。
首先,我們先在資料庫裡建立起使用者及權限相關的資料表。
CREATE TABLE [dbo].[User] (
[Id] varchar(45) NOT NULL PRIMARY KEY, --GUID
[UserId] varchar(45) NOT NULL, --使用者登入帳號
[PasswordHash] varchar(100) NOT NULL, --使用者登入密碼
[Name] varchar(10) NOT NULL, --使用者名稱
[GroupId] varchar(45), --使用者群組別
[EmpNo] varchar(10) NOT NULL --員工編號
)
CREATE TABLE [dbo].[Roles] (
[Id] varchar(45) NOT NULL PRIMARY KEY, --GUID
[Name] varchar(200) NOT NULL
)
CREATE TABLE [dbo].[UserGroup] (
[Id] varchar(45) NOT NULL PRIMARY KEY, --GUID
[Name] varchar(50) NOT NULL
)
CREATE TABLE [dbo].[GroupRoles] (
[GroupId] varchar(45) NOT NULL,
[RoleId] varchar(45) NOT NULL
)
接著建立起對應的ApplicationUser類別並且繼承至Microsoft.AspNet.Identity.IUser
public class ApplicationUser : IUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public ApplicationUser(string userId) : this()
{
this.UserId = userId;
}
public string Id { get; set; }
public string UserId { get; set; }
public string PasswordHash { get; set; }
public string Name { get; set; }
public string GroupId { get; set; }
public string EmpNo { get; set; }
public string SecurityStamp { get; set; }
}
ASP.NET Identity把所有的操作都包裝在一個叫UserStore的類別內,要使用ASP.NET Identity哪些功能,如Password、Role或Claim等機制,皆看UserStore是否有實作該項介面而定。
public class UserStore : IUserStore<ApplicationUser>,
IUserPasswordStore<ApplicationUser>,
IUserRoleStore<ApplicationUser>
{
private UserRepository userRepository;
private UserRolesRepository userRolesRepository;
public UserStore(string farmId)
{
RepositoryFactory repositoryFactory = RepositoryFactory.GetFactory(farmId);
this.userRepository = repositoryFactory.Get<UserRepository>();
this.userRolesRepository = repositoryFactory.Get<UserRolesRepository>();
}
public Task CreateAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
this.userRepository.Insert(user);
return Task.FromResult<object>(null);
}
public Task DeleteAsync(ApplicationUser user)
{
if (user != null)
{
this.userRepository.Delete(user.Id);
}
return Task.FromResult<object>(null);
}
public Task<ApplicationUser> FindByIdAsync(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentException("Null or empty argument: id");
}
ApplicationUser user = this.userRepository.Select(id);
if (user != null)
{
return Task.FromResult<ApplicationUser>(user);
}
return Task.FromResult<ApplicationUser>(null);
}
public Task<ApplicationUser> FindByNameAsync(string userId)
{
if (string.IsNullOrEmpty(userId))
{
throw new ArgumentException("Null or empty argument: userId");
}
ApplicationUser user = this.userRepository.SelectByUserName(userId);
if (user != null)
{
return Task.FromResult<ApplicationUser>(user);
}
return Task.FromResult<ApplicationUser>(null);
}
public Task<string> GetPasswordHashAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult<string>(user.PasswordHash);
}
public Task<IList<string>> GetRolesAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
IList<string> roles = this.userRolesRepository.Select(user.Id);
{
if (roles != null)
{
return Task.FromResult<IList<string>>(roles);
}
}
return Task.FromResult<IList<string>>(null);
}
public Task<bool> HasPasswordAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
var hasPassword = !string.IsNullOrEmpty(this.userRepository.Select(user.Id).PasswordHash);
return Task.FromResult<bool>(hasPassword);
}
public Task<bool> IsInRoleAsync(ApplicationUser user, string role)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if (string.IsNullOrEmpty(role))
{
throw new ArgumentNullException("role");
}
IEnumerable<string> roles = this.userRolesRepository.Select(user.Id);
if (roles != null && roles.Contains(role))
{
return Task.FromResult<bool>(true);
}
return Task.FromResult<bool>(false);
}
public Task SetPasswordHashAsync(ApplicationUser user, string passwordHash)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
user.PasswordHash = passwordHash;
return Task.FromResult<object>(null);
}
public Task UpdateAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
this.userRepository.Update(user);
return Task.FromResult<object>(null);
}
public Task RemoveFromRoleAsync(ApplicationUser user, string role)
{
throw new NotImplementedException();
}
public Task AddToRoleAsync(ApplicationUser user, string role)
{
throw new NotImplementedException();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
// do nothing.
}
}
上面的RepositoryFactory,是系統架構中對儲存層的存取方式,之後會再做更詳細的說明,現在可以先想像成可以直接取得資料庫中的User及Role的資料。
實際上我們並不會直接使用UserStore,在ASP.NET Identity的機制中,還需要透過UserManager類別來呼叫,我們再將它的操作包裝成IdentityHelper以簡化新增使用者、登入及登出等操作。
public class IdentityHelper : IDisposable
{
private UserManager<ApplicationUser> userManager;
public IdentityHelper(string farmId)
{
this.userManager = new UserManager<ApplicationUser>(new UserStore(farmId));
}
private static IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.Current.GetOwinContext().Authentication;
}
}
public static void LogOff()
{
AuthenticationManager.SignOut();
}
public IdentityResult Register(ApplicationUser user, string password)
{
return this.userManager.Create(user, password);
}
public IdentityResult Register(string userId, string password, string name)
{
return this.Register(
new ApplicationUser()
{
UserId = userId,
Name = name,
},
password);
}
public void SignIn(ApplicationUser user)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = this.userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);
}
public bool SignIn(string userId, string password)
{
ApplicationUser user = this.userManager.Find(userId, password);
if (user == null)
{
return false;
}
this.SignIn(user);
return true;
}
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool flag)
{
this.userManager.Dispose();
GC.SuppressFinalize(this);
}
}
基本上,我們現在已經有了一個透過ASP.NET Identity來做新增使用者、登入及登出系統的基礎架構了。